
function Compiler ()
{
    this.compile=compile;

    var r="";

    var indention = 0;

    function indent ()
    {
	for (var i = 0; i < indention; i++)
	    tab();
    }

    function put (string)
    {
	r+=string;
    }

    function sep() 
    {
	r+=" ";
    }

    function comma ()
    {
	r+=", "; 
    }

    function newline ()
    {
	r+="\n";
    }

    function tab ()
    {
	r+="    ";
    }

    function newlineAndIndent()
    {
	newline();
	indent();
    }

    function compile (exp,env)
    {
	r = "";
	compileExp(exp,env);
	return r;
    }

    function compileExp (exp,env)
    {
	if (atom(exp))
	    compileAtom(exp,env);
	else
	    compileCompound(exp,env);
    }

    function compileIfStatement(test,then,Else)
    {
	put ("if (");
	compileExp(test);
	put (")");
	indention++;
	newlineAndIndent();
	compileExp(then);
	indention--;
	if (Else) {
	    newlineAndIndent();
	    put ("else");
	    indention++;
	    newlineAndIndent();
	    compileExp (Else);
	    indention--;
	}	
    }

    function compileApplication(exp,env)
    {
	compileExp(first(exp),env);
	put("(");
	exp=(rest(exp));
	while (exp) {
	    compileExp(first(exp));
	    if (rest(exp))
		comma();
	    exp=(rest(exp));
	}
	put(")");
    }

    function compileExp (exp,env)
    {
	if (atom(exp))
	    compileAtom(exp,env);
	else
	    compileCompound(exp,env);
    }

    function compileCompound (exp,env)
    {
	if (!first(exp))
	    return null;
	var args = rest(exp);
	if (symbolp(first(exp))) {
	    var name = first(exp).name;
	    switch (name) {
	    case "do":
		break;
	    case "if":
		compileIfStatement(first(args),second(args), third(args));
		break;
	    case "def":
		if (consp(first(args))) 
		    compileNamedFunction(first(first(args)),
					 rest(first(args)),
					 rest(args),
					 env);
					 
		else 
		    compileDef(first(args),second(args));
		break;
	    case "fn":
		compileFunction(first(args), rest(args),
				new Env(env));
		break;
	    default:
		compileApplication(exp,env);
	    }
	}
    }

    function compileStatement(exp,env)
    {

    }

    function compileExpression(exp,env)
    {

    }

    function compileDef(symbol,body)
    {
	compileAtom(symbol);
	put (" = ");
	compileExp(body);
	put (";");
    }

    function compileNamedFunction (name, args, body, env)
    {
	put("function ");
	compileExp(name);
	sep();
	compileArgs(args);
	compileBody(body,env);
    }
    
    function compileFunction(args,body,env)
    {
	put("function ");
	compileArgs(args);
	compileBody(body,env);
    }

    function compileArgs (args)
    {
	put("(");
	while (args) {
	    var symbol = first(args);
	    if (!symbolp(symbol))
		throw("Only symbols are allowed for arguments.");
	    put (symbol.name);
	    if (rest(args))
		comma();
	    args=rest(args);
	}
	put(") ");
    }

    function compileBody(body,env)
    {
	put("{");
	indention++;
	while (body) {
	    newlineAndIndent();
	    /* No implicit return.
	    if (!rest(body))
	        put ("return ");
	    */
	    compileExp(first(body));
	    put(";")
	    body=rest(body);
	}
	indention--;
	newlineAndIndent();
	put("}");
    }

    function compileAtom(atom,env)
    {
	if (symbolp(atom))
	    compileSymbol(atom,env);
	else
	    compileConstant(atom);
    }

    function compileSymbol(symbol,env)
    {
	put(symbol.name);
    }

    function compileConstant (constant)
    {
	put (constant);
    }

}